// #### 1. 变量的解构赋值
// 3 - 1. 数组的解构赋值
let [a, b, c] = [1, 2, 3]
// - 模式匹配->只要等号两边的模式相同，左边的变量就会被赋予对应的值
let [foo, [
  [bar], baz
]] = [1, [
  [2], 3
]]
foo // 1
bar // 2
baz // 3

let [, , third] = ['foo', 'bar', 'baz']
third // baz

let [x, , y] = [1, 2, 3]
x // 1
y // 3

let [head, ...third] = [1, 2, 3, 4]
head // 1
tail // [2, 3, 4]
let [x, y, ...z] = ['a']
x // 'a'
y // undefined 赋值不成功，变量的值为undefined
z // []

// - 不安全解构->左边的模式，只匹配一部分的等号右边的数组
let [x, y] = [1, 2, 3]
x // 1
y // 2

let [a, [b], d] = [1, [2, 3], 4]
a // 1
b // 2
d // 4

// - 等号右边不是数组（不可遍历的解构）,将报错
// 报错示例
let [foo] = 1
let [foo] = null
let [foo] = {}

// - `Set`结构也可以使用数组的解构赋值
let [x, y, z] = new Set(['a', 'b', 'c'])
x // 'a'

// - 只要具备 `Iterator`接口，都可以采用数组的解构赋值
function* fibs() {
  let a = 0
  let b = 1
  while (true) {
    yield a[a, b] = [b, a + b]
  }
}

let [first, second, third, fourth, fifth, sixth] = fibs()
sixth // 5

// 3 - 1 - 2. 默认值
// - 解构赋值允许默认值
let [foo = true] = []
foo // true

let [x, y = 'b'] = ['a'] // x = 'a', y = 'b'
let [x, y = 'b'] = ['a', undefined] // x = 'a', y = 'b'
// - ES6用===严格等于来判断一个位置是否有值，所以一个数组成员严格等于`undefined`，默认值生效
let [x = 1] = [undefined]
x // 1
let [x = 1] = [null]
x // null 

// - 表达式的惰性求值
function f() {
  console.log('aaa')
}
let [x = f()] = [1]
x // 1 函数f并不执行

let x
if ([1][0] === [undefined]) {
  x = f()
} else {
  x = [1][0]
}

// - 默认值客引用解构赋值的其他已经声明的变量
let [x = 1, y = x] = [] // x=1, y=1
let [x = 1, y = x] = [2] // x=2, y=2
let [x = 1, y = x] = [1, 2] // x=1, y=2
let [x = y, y = 1] = [] // ReferenceError: y is not defined

// ##### 3 - 2. 对象的解构赋值
// 3 - 2 - 1. 基本用法
// - 对象解构，变量必须与属性同名，此啊能取到正确的额值
let {
  foo,
  bar
} = {
  foo: 'aaa',
  bar: 'bbb'
}
foo // 'aaa'
bar // 'bbb'

// - 属性名次序不一致，取值无影响
let {
  bar,
  foo
} = {
  foo: 'aaa',
  bar: 'bbb'
}
foo // 'aaa'
bar // 'bbb'
let {
  baz
} = {
  foo: 'aaa',
  bar: 'bbb'
}
baz // undefined

// - 变量名与属性名不一致
let {
  foo: baz
} = {
  foo: 'aaa',
  bar: 'bbb'
}
baz // 'aaa'

let obj = {
  first: 'hello',
  last: 'world'
}
let {
  first: f,
  last: l
} = obj
f // 'hello'
l // 'world'

// - 实际说明，对象解构赋值机制，先找到同名属性，在赋给对应的变量，真正被赋值的是后者。
let {
  foo: baz
} = {
  foo: 'aaa',
  bar: 'bbb'
}
baz // 'aaa'
foo // error: foo is not defined
// foo是匹配模式，baz才是变量

// - 数组一样，解构也可以用于嵌套结构的对象
let obj = {
  p: [
    'hello',
    {
      y: 'World'
    }
  ]
}

let {
  p: [x, {
    y
  }]
} = obj
x // 'hello'
y // 'World'
let {
  p,
  p: [x, {
    y
  }]
} = obj
x // 'hello'
y // 'World'
p // ['hello, { y: 'World'}]

const node = {
  loc: {
    start: {
      line: 1,
      column: 5
    }
  }
}
let {
  loc,
  loc: {
    start
  },
  loc: {
    start: {
      line
    }
  }
} = node
line // 1
loc // Object { start: Object}
start // Object { line: 1, column: 5}

let obj = {}
let arr = []
  ({
    foo: obj.prop,
    bar: arr[0]
  } = {
    foo: 123,
    bar: true
  })
obj // {prop: 123}
arr // [true]

// 3 - 2 - 2. 默认值
let {
  x = 3
} = {}
x // 3
let {
  x,
  y = 5
} = {
  x: 1
}
x // 1
y // 5
let {
  x: y = 3
} = {}
y // 3
let {
  x: y = 3
} = {
  x: 5
}
y // 5
let {
  message: msg = 'Something went wrong'
} = {}
msg //  'Something went wrong'

// - 默认值生效的条件是： 对象的属性值严格等于undefined
let {
  x = 3
} = {
  x: undefined
}
let {
  x = 3
} = {
  x: null
}
x // null null与undefined不严格等于

// - 结构失败，变量的值等于`undefined`
let {
  foo
} = {
  bar: 'baz'
}
foo // undefined
// - 如果解构模式是嵌套对象，而且自对象所在的副属性不存在，那么将会报错
// 报错
let {
  foo: {
    bar
  }
} = {
  baz: 'baz'
}
// 编译效果
let _tmp = {
  baz: 'baz'
}
_tmp.foo.bar

// - 将一个已经声明的变量用于解构赋值，必须非常小心
// 错误的写法
// let x
// { x } = { x: 1} // SyntaxError: syntax error

// 正确的写法
let x({
  x
} = {
  x: 1
})

// - 对象解构赋值，可以方便地姜现有对象的方法，赋值给某个变量
let {
  log,
  sin,
  cos
} = Math

// ##### 3 - 3. 字符串的结构赋值
// - 字符串被转化成类似数组的对象
const [a, b, c, d, e] = 'hello'
a // h
b // e
c // l
d // l
e // o
// - 类似数组的对象都有一个`length`属性
let {
  length: len
} = 'hello'
len // 5

// ##### 3 - 4. 数值和布尔值的解构赋值
// - 解构时，等号右边是数值和布尔值，则会先转对象
let {
  toString: s
} = 123
s === Number.prototype.toString // true
let {
  toString: s
} = true
s === Number.prototype.toString // true

// - `undefined`和`null`不能转为对象，所以解构赋值会报错
let {
  prop: x
} = undefined // TypeError
let {
  prop: y
} = null // TypeError

// ##### 3 - 5. 函数参数的解构赋值
function add([x, y]) {
  return x + y
}
add([1, 2])

[[1, 2], [3, 4]].map(([a, b]) => a + b)
// [3, 7]
// - 函数参数的解构也可以使用默认值

function move({
  x = 0,
  y = 0
} = {}) {
  return [x, y]
}
move({
  x: 3,
  y: 8
}) // [3, 8]
move({
  x: 3
}) // [3, 0]
move({}) // [0, 0]
move() // [0, 0]

// 对比
function move({
  x,
  y
} = {
  x: 0,
  y: 0
}) {
  return [x, y]
}
move({
  x: 3,
  y: 8
}) // [3, 8]
move({
  x: 3
}) // [3, undefined]
move({}) // [undefined, undefined]
move()[0, 0]

// - `undefined`触发函数的默认值
[1, undefined, 3].map((x = 'yes') => x)
// [1, 'yes', 3]

// ##### 3 - 6.圆括号问题
// 3 - 6 - 1. 不能用圆括号的三种情况
// - 变量声明语句
// 全部报错
// let [(a)] = [1]
// let {x: (c)} = {}
// let ({x: c}) = {}
// let {(x: c)} = {}
// let {(x): c} = {}
// let ( o: ({p: p}) ) = {0: {p: 2}}

// - 函数参数
// 报错
// function f([(z)]) {return z}
// function f([z, {x}]) { return x}

// - 赋值语句的模式
// 报错
// ({p: a}) = {p: 42}
// ([a]) = [5]

// 3 - 6 - 2. 可以使用圆括号
// - 赋值语句的非模式部分
[(b)] = [3]
({
  p: (d)
} = {})[(parseInt.prop)] = [3]

// ##### 3 - 7. 用途
// - 变量交换的值
let x = 1
let y = 2[x, y] = [y, x]

// - 从函数返回多个值
function example() {
  return [1, 2, 3]
}
let [a, b, c] = example()

function example() {
  return {
    foo: 1,
    bar: 2
  }
}

let {
  foo,
  bar
} = example()

// - 函数参数的定义
function f([x, y, z]) {
  // ...
}
f([1, 2, 3])

function f({
  x,
  y,
  z
}) {
  // ...
}
f({
  z: 3,
  y: 2,
  x: 1
})

// - 提取`JSON`数据
let jsonData = {
  id: 42,
  status: 'ok',
  data: [867, 5300]
}

let {
  id,
  status,
  data: number
} = jsonData
console.log(id, status, number)
// 42, 'ok', [867, 5300]

// - 函数参数的默认值
// 避免了 let foo = config.foo || 'default foo'
jQuery.ajax = function (url, {
  async = true,
  beforeSend = function () {},
  cache = true,
  complete = function () {},
  crossDomain = false,
  global = true,
} = {}) {
  // ...
}

// - 遍历`Map`结构
const map = new Map()
map.set('first', 'hello')
map.set('second', 'world')

for (let [key, balue] of map) {
  console.log(key + " is " + value)
}

for (let [key] of map) {
  // ...
}

for (let [, value] of map) {
  // ...
}

// - 输入模块的指定方法
const {
  SourceMapConsumer,
  SourceNode
} = require('source-map')